// AudioEngine.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "AudioEngine.h"

// Registry key used by Avian Play
#define REG_KEY _T("Software\\Avian Waves\\Avian Play\\Audio Engine")

// Registry values that are output back to .Net
#define REG_ENGTITLE          _T("_EngineTitle")       // [String] Stores the current title of the window at startup -- used for locating the AP Engine.
#define REG_ENGCLASS          _T("_EngineClass")       // [String] Stores the current class of the window at startup -- used for locating the AP Engine.

#define REG_STREAMTITLE       _T("StreamTitle")        // [String] Value that stores the current stream value, valid only if we are playing and streaming.
#define REG_STREAMTITLESEQ    _T("_SEQStreamTitle")    // [DWORD] Sequence number that increments any time StreamTitle is updated and ready to be consumed.

#define REG_STREAMSTATION     _T("StreamStation")      // [String] Value that stores the current stream station name, valid only if we are playing and streaming.
#define REG_STREAMSTATIONSEQ  _T("_SEQStreamStation")  // [DWORD] Sequence number that increments any time StreamStation is updated and ready to be consumed.

#define REG_MADSTATE          _T("MADState")           // [DWORD] Current state of the MAD audio engine.
#define REG_MADERROR          _T("MADError")           // [DWORD] Error code of the MAD audio engine, if an error occurred.
#define REG_MADSEQSTATE       _T("_SEQMADState")       // [DWORD] Sequence number that increments any time MADState is updated and ready to be consumed.

#define REG_MADSTREAMSTATE    _T("MADStreamState")     // [DWORD] Current stream state of the MAD audio engine, valid only if we have a stream loaded.
#define REG_MADSEQSTREAMSTATE _T("_SEQMADStreamState") // [DWORD] Sequence number that increments any time MADStreamState is updated and ready to be consumed.

#define REG_CONTROLERROR      _T("ControlError")       // [DWORD] Error code raised if a control action fails.
#define REG_CONTROLERRORSEQ   _T("_SEQControlError")   // [DWORD] Control action failure sequence number

#define REG_TRACKTIME         _T("TrackTime")          // [DWORD] Current time index of current file (in seconds), valid only if we have a file loaded.
#define REG_TRACKDURATION     _T("TrackDuration")      // [DWORD] Total length of current file (in seconds), valid only if we have a file loaded.
#define REG_BITRATE           _T("BitRate")            // [DWORD] Kbps

// Registry values that are input
#define REG_FILENAME          _T("inFileName")         // [String] Filename to open (used for MAD_OpenFile)
#define REG_STREAMURL         _T("inStreamURL")        // [String] URL to open (used for MAD_OpenStream)

// Valid messages
#define WM_STARTENGINE WM_USER + 1
#define WM_ENDENGINE   WM_USER + 2
#define WM_CONTROL     WM_USER + 3

// Custom errors
#define MAP_ERROR_FILE_OPEN   999900
#define MAP_ERROR_URL_OPEN    999901
#define MAP_ERROR_PLAY        999902

// Global Variables:
HINSTANCE   		 g_hInst;			// current instance
HWND             g_hwnd = NULL;
DWORD            g_streamseqtitle = 0;
DWORD            g_streamseqstation = 0;
DWORD            g_madseqstate = 0;
DWORD            g_madseqstreamstate = 0;
DWORD            g_madseqpos = 0;
DWORD            g_controlseqpos = 0;
HANDLE           hLib = NULL;
MAP_INFORMATION  g_mi;
BOOL             g_lastIsStream = FALSE;

// Forward declarations of functions included in this code module:
BOOL			        InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
void MessageLoop();
void StartEngine();
void EndEngine();
void RegGetString(TCHAR *valueName, TCHAR *value, int length);
void RegSetString(TCHAR *valueName, TCHAR *value, int length);
void RegGetDWORD(TCHAR *valueName, DWORD *value);
void RegSetDWORD(TCHAR *valueName, DWORD *value);
void SetError(DWORD value);
void RefreshMAPInformation();
void ZeroRegistry();
void GetStreamTitle();
void GetStreamStation();
void GetDuration();
void OpenFile();
void OpenFileNext();
void OpenURL();

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
  // Perform application initialization:
  if (!InitInstance(hInstance, nCmdShow)) 
  {
    return 0;
  }

  ::SetThreadPriority(::GetCurrentThread, THREAD_PRIORITY_ABOVE_NORMAL);

  // We eat all exceptions, so the user hopefully won't get the application error report
  try
  {
    MessageLoop();
  }
  catch(...)
  {
  }

  return 0;
}

void MessageLoop()
{
  MSG msg;

  __try
  {
    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0) > 0) 
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  __except(1)
  {
  }
}

ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)
{
  WNDCLASS wc;

  wc.style         = 0;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = NULL;
  wc.hCursor       = 0;
  wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
  wc.lpszMenuName  = 0;
  wc.lpszClassName = szWindowClass;

  return RegisterClass(&wc);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
  HWND hWnd;
  TCHAR szTitle[MAX_PATH + 1] = _T("Avian Play Audio Engine");		// title bar text
  TCHAR szWindowClass[MAX_PATH + 1] = _T("AvianPlayAudioEngineClass");	// main window class name

  g_hInst = hInstance; // Store instance handle in our global variable

  // SHInitExtraControls should be called once during your application's initialization to initialize any
  // of the device specific controls such as CAPEDIT and SIPPREF.
  SHInitExtraControls();

  //If it is already running, then exit
  hWnd = FindWindow(szWindowClass, szTitle);	
  if (hWnd) 
  {
    return FALSE;
  } 

  // Attempt to register the window class
  if (!MyRegisterClass(hInstance, szWindowClass))
  {
    return FALSE;
  }

  // Create the window disabled (invisible) and out of bounds (just in case)
  hWnd = CreateWindow(szWindowClass, szTitle, WS_DISABLED, -100, -100, 0, 0, NULL, NULL, hInstance, NULL);

  if (!hWnd)
  {
    return FALSE;
  }

  // Update window to "minimized" mode (again, just in case)
  ShowWindow(hWnd, SW_MINIMIZE);
  UpdateWindow(hWnd);
  g_hwnd = hWnd;

  // Write out the window class/title
  RegSetString(REG_ENGTITLE, szTitle, MAX_PATH);
  RegSetString(REG_ENGCLASS, szWindowClass, MAX_PATH);

  return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  DWORD dwparam = wParam;
  DWORD dlparam = lParam;
  DWORD value;
  MAP_OPTIONS mo;

  switch (message) 
  {
  case MAP_MSG_STATUS:
    RegSetDWORD(REG_MADSTATE, &dwparam);
    RegSetDWORD(REG_MADERROR, &dlparam);
    g_madseqstate ++;
    RegSetDWORD(REG_MADSEQSTATE, &g_madseqstate);
    if (g_lastIsStream)
    {
      DWORD strmstat = MAP_GetStreamingStatus(hLib);
      RegSetDWORD(REG_MADSTREAMSTATE, &strmstat);
      g_madseqstreamstate ++;
      RegSetDWORD(REG_MADSEQSTREAMSTATE, &g_madseqstreamstate);
    }
    break;

  case MAP_MSG_STREAM_TITLE:
    GetStreamStation();
    GetStreamTitle();
    if (g_lastIsStream)
    {
      DWORD strmstat = MAP_GetStreamingStatus(hLib);
      RegSetDWORD(REG_MADSTREAMSTATE, &strmstat);
      g_madseqstreamstate ++;
      RegSetDWORD(REG_MADSEQSTREAMSTATE, &g_madseqstreamstate);
    }
    break;

  case WM_STARTENGINE:
    StartEngine();
    break;

  case WM_ENDENGINE:
    EndEngine();
    break;

  case WM_CONTROL:
    if (hLib != NULL)
    {
      switch (wParam)
      {
      case 0: // Play
        if (!MAP_Play(hLib) && MAP_GetStatus(hLib) == MAP_STATUS_STOP) // Check to see if status is STOP because sometimes with streams, Play will erroneously exit with error, but playback continues anyway!
        {
          SetError(MAP_ERROR_PLAY);
        }
        break;
      case 1: // Pause
        MAP_Pause(hLib);
        break;
      case 2: // Stop
        MAP_Stop(hLib);
        break;
      case 3: // Open File
        OpenFile();
        break;
      case 4: // Open URL
        OpenURL();
        break;
      case 5: // Get Stream Title
        GetStreamTitle();
        break;
      case 6: // Get Stream Station Name
        GetStreamStation();
        break;
      case 7: // Get Stream Status
        MAP_GetStreamingStatus(hLib); 
        break;
      case 8: // Get Duration of File
        GetDuration();
        break;
      case 9: // Get Current Position of File
        value = MAP_GetCurrent(hLib);
        RegSetDWORD(REG_TRACKTIME,&value);
        break;
      case 10: // Refresh all MAP information (automatically happens at file/url load)
        RefreshMAPInformation();
        break;
      case 11: // Skip ahead
        MAP_Ff(hLib, lParam);
        break;
      case 12: // Skip back
        MAP_Rew(hLib, lParam);
        break;
      case 13: // Seek
        MAP_Seek(hLib, lParam);
        break;
      case 14: // Close current file
        MAP_Close(hLib);
        break;
      case 15: // Set volume
        if (lParam > 255) lParam = 255;
        if (lParam < 0) lParam = 0;
        MAP_SetChannelVolume(hLib, (BYTE) lParam);
        break;
      case 16: // Set prebuffer percentage
        if (lParam > 100) lParam = 100;
        if (lParam < 0) lParam = 0;
        MAP_GetOptions(hLib, &mo);
        mo.nOutputPrebuffer = (int) lParam;
        MAP_SetOptions(hLib, &mo);
        break;
      case 17: // Set buffer length
        if (lParam > 5000) lParam = 5000;
        if (lParam < 20) lParam = 20;
        MAP_GetOptions(hLib, &mo);
        mo.nOutputBufferLen = (int) lParam;
        MAP_SetOptions(hLib, &mo);
        break;
      }
      break;
    }
  case WM_DESTROY:
    EndEngine();
    PostQuitMessage(0);
    break;

  default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}

void SetError(DWORD value)
{
  RegSetDWORD(REG_CONTROLERROR, &value);
  g_controlseqpos ++;
  RegSetDWORD(REG_CONTROLERRORSEQ, &g_controlseqpos);
}

void GetDuration()
{
  DWORD value = MAP_GetDuration(hLib);
  RegSetDWORD(REG_TRACKDURATION,&value);
}

void GetStreamTitle()
{
  if (hLib)
  {
    TCHAR title[MAX_PATH + 1] = _T("");

    MAP_GetStreamTitle(hLib, title);

    RegSetString(REG_STREAMTITLE, title, MAX_PATH);
    g_streamseqtitle ++;
    RegSetDWORD(REG_STREAMTITLESEQ, &g_streamseqtitle);
  }
}

void GetStreamStation()
{
  if (hLib)
  {
    TCHAR station[MAX_PATH + 1] = _T("");

    MAP_GetStreamInfo(hLib, station, NULL, NULL);

    RegSetString(REG_STREAMSTATION, station, MAX_PATH);
    g_streamseqstation ++;
    RegSetDWORD(REG_STREAMSTATIONSEQ, &g_streamseqstation);
  }
}

void RefreshMAPInformation()
{
  if (hLib)
  {
    MAP_GetFileInfo(hLib, &g_mi);
    DWORD val = g_mi.nBitRate;
    RegSetDWORD(REG_BITRATE, &val);
  }
}

void OpenFile()
{
  if (hLib)
  {
    TCHAR path[MAX_PATH + 1] = _T("");
    RegGetString(REG_FILENAME, path, MAX_PATH);
    MAP_Stop(hLib);
    g_lastIsStream = FALSE;
    if (!MAP_Open(hLib, path)) 
    {
      SetError(MAP_ERROR_FILE_OPEN);
    }
    else
    {
      RefreshMAPInformation();
      GetDuration();
    }
  }
}

void OpenURL()
{
  if (hLib)
  {
    TCHAR path[MAX_PATH + 1] = _T("");
    RegGetString(REG_STREAMURL, path, MAX_PATH);
    MAP_Stop(hLib);
    g_lastIsStream = TRUE;
    if (!MAP_OpenURL(hLib, path)) 
    {
      SetError(MAP_ERROR_URL_OPEN);
    }
    else
    {
      RefreshMAPInformation();
      DWORD strmstat = MAP_GetStreamingStatus(hLib);
      RegSetDWORD(REG_MADSTREAMSTATE, &strmstat);
      g_madseqstreamstate ++;
      RegSetDWORD(REG_MADSEQSTREAMSTATE, &g_madseqstreamstate);
      DWORD val = 0;
      RegSetDWORD(REG_TRACKDURATION, &val);
    }
  }
}

void ZeroRegistry()
{
  // Empty out all the strings (except the engine window name/class strings and input strings)
  TCHAR *zeroString = _T("");
  RegSetString(REG_STREAMTITLE, zeroString, 0);
  RegSetString(REG_STREAMSTATION, zeroString, 0);

  // Zero the sequence and relevant variables depending on seqences
  DWORD zero = 0;

  RegSetDWORD(REG_MADSTATE, &zero);
  RegSetDWORD(REG_MADERROR, &zero);
  RegSetDWORD(REG_MADSTREAMSTATE, &zero);
  RegSetDWORD(REG_CONTROLERROR, &zero);

  g_streamseqstation = 0;
  g_streamseqtitle = 0;
  g_madseqstate = 0;
  g_madseqstreamstate = 0;
  g_madseqpos = 0;
  g_controlseqpos = 0;

  RegSetDWORD(REG_STREAMSTATIONSEQ, &g_streamseqstation);
  RegSetDWORD(REG_STREAMTITLESEQ, &g_streamseqtitle);
  RegSetDWORD(REG_MADSEQSTATE, &g_madseqstate);
  RegSetDWORD(REG_MADSEQSTREAMSTATE, &g_madseqstreamstate);
  RegSetDWORD(REG_CONTROLERRORSEQ, &g_controlseqpos);

  // Zero the direct properties
  RegSetDWORD(REG_BITRATE, &zero);
  RegSetDWORD(REG_TRACKDURATION, &zero);
  RegSetDWORD(REG_TRACKTIME, &zero);
}

void StartEngine()
{
  MAP_OPTIONS mo;

  if (hLib == NULL)
  {
    // Zero out all registry values for IPC
    ZeroRegistry();

    // Initialize the audio engine
    hLib = MAP_Initialize();

    // Fetch the current (default) settings and augment them slightly
    MAP_GetOptions(hLib, &mo);
    mo.fFadeIn = FALSE;
    mo.nOutputPrebuffer = 50;
    mo.nOutputBufferLen = 500;
    mo.nThreadPriority = THREAD_PRIORITY_HIGHEST;
    MAP_SetOptions(hLib, &mo);

    // Set the message window for IPC and max out the volume as the default volume level
    MAP_SetMessageWindow(hLib, g_hwnd);
    MAP_SetChannelVolume(hLib, (BYTE) 255);
  }
}

void EndEngine()
{
  if (hLib != NULL)
  {
    MAP_SetMessageWindow(hLib, NULL);
    MAP_Uninitialize(hLib);
    hLib = NULL;
  }
}

void RegGetString(TCHAR *valueName, TCHAR *value, int length)
{
  HKEY hk;
  DWORD disp = 0;
  DWORD maxBytes = 0;

  RegCreateKeyEx(HKEY_CURRENT_USER, REG_KEY, 0, _T(""), 0, 0, NULL, &hk, &disp);
  if (hk != NULL)
  {
    maxBytes = length * sizeof(TCHAR);
    if (RegQueryValueEx(hk, valueName, 0, NULL, (LPBYTE) value, &maxBytes))
    {
      RegSetValueEx(hk, valueName, 0, REG_SZ, (LPBYTE) value, maxBytes);
    }

    RegCloseKey(hk);
  }
}

void RegSetString(TCHAR *valueName, TCHAR *value, int length)
{
  HKEY hk;
  DWORD disp = 0;
  DWORD maxBytes = 0;

  if (length < 0) length = 0;
  size_t strLen = _tcslen(value);
  if (strLen > (size_t) length) strLen = length;

  RegCreateKeyEx(HKEY_CURRENT_USER, REG_KEY, 0, _T(""), 0, 0, NULL, &hk, &disp);
  if (hk != NULL)
  {
    maxBytes = strLen * sizeof(TCHAR);
    RegSetValueEx(hk, valueName, 0, REG_SZ, (LPBYTE) value, maxBytes);
    RegCloseKey(hk);
  }
}

void RegGetDWORD(TCHAR *valueName, DWORD *value)
{
  HKEY hk;
  DWORD disp = 0;
  DWORD maxBytes = 0;

  RegCreateKeyEx(HKEY_CURRENT_USER, REG_KEY, 0, _T(""), 0, 0, NULL, &hk, &disp);
  if (hk != NULL)
  {
    maxBytes = sizeof(value);
    if (RegQueryValueEx(hk, valueName, 0, NULL, (LPBYTE) value, &maxBytes))
    {
      RegSetValueEx(hk, valueName, 0, REG_DWORD, (LPBYTE) value, maxBytes);
    }

    RegCloseKey(hk);
  }
}

void RegSetDWORD(TCHAR *valueName, DWORD *value)
{
  HKEY hk;
  DWORD disp = 0;
  DWORD maxBytes = 0;

  RegCreateKeyEx(HKEY_CURRENT_USER, REG_KEY, 0, _T(""), 0, 0, NULL, &hk, &disp);
  if (hk != NULL)
  {
    maxBytes = sizeof(value);
    RegSetValueEx(hk, valueName, 0, REG_DWORD, (LPBYTE) value, maxBytes);
    RegCloseKey(hk);
  }
}

